package com.clock.service.impl;

import com.clock.common.constant.HttpStatus;
import com.clock.common.constant.SignCacheKey;
import com.clock.common.domain.R;
import com.clock.common.exception.BizException;
import com.clock.common.utils.LocalDateTimeUtil;
import com.clock.common.utils.RedisUtil;
import com.clock.domain.entity.User;
import com.clock.domain.vo.SignRankInfoVO;
import com.clock.mapper.UserMapper;
import com.clock.service.SignInService;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service
public class SignInServiceImpl implements SignInService {

    @Resource
    RedisUtil redisUtil;

    @Resource
    RedisTemplate redisTemplate;

    @Resource
    UserMapper userMapper;



    /**
     * 检测是否签到（查找是否存在该key）
     *
     * @param userSignKey 缓存Key
     * @param offset 偏移量
     * @return
     */
    private Boolean checkSign(String userSignKey, long offset) {
        return redisUtil.getBit(userSignKey, offset);
    }


    /**
     * 补签
     * @param userId
     * @param day
     * @return
     */
    @Override
    public R insertSignByDay(Long userId, Integer day) {

        //用户签到key(月活)
        String userSignKey = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "month:" + LocalDateTimeUtil.getYYYYMMLocalDate() + ":userId:" + Long.valueOf(userId);

        //日活统计key
        String usersDailyLivingKey = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "day:" + LocalDateTimeUtil.getYYYYMMDDLocalDate();

        //签到排行榜key
        String RANK_KEY= SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "rank"+":month:" + LocalDateTimeUtil.getYYYYMMLocalDate() ;

        if (day == null || day < 0 || day > 31) {
            throw new BizException(HttpStatus.BAD_REQUEST, "天数异常");
        }
        //时间偏移量
        long offsetOfTime = day-1;

        //判断是否重复签到
        if (checkSign(userSignKey, offsetOfTime)) {
            throw new BizException(HttpStatus.CONFLICT, "重复补签");
        }
        //正常签到写入缓存设置TTL为31D
        try {

            //Todo:是否需要判断处于该月，是用生存时间好还是过期时间好
            //个人签到写入缓存
            redisUtil.setBitForTTL(userSignKey, offsetOfTime, true, 31);


            //写入日活人数缓存
            redisUtil.setBitForTTL(usersDailyLivingKey, userId, true, 31);

            redisUtil.incrScore(RANK_KEY, String.valueOf(userId), 1);


        }catch (Exception e) {
            throw new BizException(HttpStatus.NOT_MODIFIED, "补签失败");
        }
        return R.ok("补签成功");
    }

    /**
     * 用户当日签到
     *
     * @author flm
     * @param userId
     * @return
     */
    @Override
    public R insertSignByUserId(Long userId) throws BizException {


        //用户签到key(月活)
        String userSignKey = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "month:" + LocalDateTimeUtil.getYYYYMMLocalDate() + ":userId:" + Long.valueOf(userId);

        //日活统计key
        String usersDailyLivingKey = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "day:" + LocalDateTimeUtil.getYYYYMMDDLocalDate();

        //签到排行榜key
        String RANK_KEY= SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "rank"+":month:" + LocalDateTimeUtil.getYYYYMMLocalDate() ;

        System.out.println("用户签到key(月活):"+userSignKey);

        System.out.println("日活统计key:"+usersDailyLivingKey);

        System.out.println("排行榜key" + RANK_KEY);


        //时间偏移量
        long offsetOfTime = Long.valueOf(LocalDateTimeUtil.getDDLocalDate()) - 1;

        //用户偏移量
        long offsetOfUserId = Long.valueOf(userId);

        //判断是否重复签到
        if (checkSign(userSignKey, offsetOfTime)) {
            throw new BizException(HttpStatus.CONFLICT, "重复签到");
        }

        //连续签到次数统计
        int checkCount = 0;

        System.out.println("个人签到key："+userSignKey);

        //正常签到写入缓存设置TTL为31D
        try {

            //Todo:是否需要判断处于该月，是用生存时间好还是过期时间好
            //个人签到写入缓存
            redisUtil.setBitForTTL(userSignKey, offsetOfTime, true, 31);

            //连续签到统计
            checkCount = getContinueSignCount(userId, offsetOfTime);

            //写入日活人数缓存
            redisUtil.setBitForTTL(usersDailyLivingKey, offsetOfUserId, true, 31);

            redisUtil.incrScore(RANK_KEY, String.valueOf(userId), 1);


        }catch (Exception e) {
            e.printStackTrace();
            throw new BizException(HttpStatus.NOT_MODIFIED, "签到失败");
        }
        return R.ok(checkCount,"签到成功");

    }

    /**
     * 获取今日的签到人数
     * @author flm
     * @return
     */
    @Override
    public R getAllSignAmountAtSameDay() throws BizException {

        //日活统计key
        String usersDailyLivingKey = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "day:" + LocalDateTimeUtil.getYYYYMMDDLocalDate();

        System.out.println("日活统计key："+usersDailyLivingKey);

        //获取今日签到人数
        Long amount = redisUtil.bitCount(usersDailyLivingKey);

        return R.ok(amount);
    }


    /**
     * 获取连续签到次数
     *
     * @author flm
     * @param userId 用户id
     * @param date  截止时间
     * @return
     */
    private int getContinueSignCount(Long userId, long date){

        //签到key
        String userSignKey = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "month:" + LocalDateTimeUtil.getYYYYMMLocalDate() + ":userId:" + Long.valueOf(userId);

        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned((int) date))
                .valueAt(0);

        List<Long> list = redisTemplate.opsForValue().bitField(userSignKey, bitFieldSubCommands);

        System.out.println("判断连续签到key: "+userSignKey);

        //为空则没有连续签到
        if (list == null || list.isEmpty()) {
            return 0;
        }

        int signCount = 0;

        //当月最后签到日期
        long signDate = list.get(0) == null ? 0 : list.get(0);

        //遍历判断连续签到情况：从今日往前遍历到月初
        for (long i = date; i >= 0; i--) {

            //先右移再左移，若两次位运算的结果与初始状态一致说明该天未签到
            if (signDate >> 1 << 1 == signDate) {

                //该天内没签到且不是最后签到日期 说明有中断了连续签到
                if (i != signDate) break;
            } else {
                signCount++;
            }

            //右移一位进入前一天
            signDate >>= 1;
        }
        return signCount;
    }


    /**
     * 获取用户当月签到次数
     *
     * @param userId 用户id
     * @return
     */
    @Override
    public R getSignCount(Long userId) {

        //月活统计key
        String userMothLivingKey = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "month:" + LocalDateTimeUtil.getYYYYMMLocalDate() + ":userId:" + Long.valueOf(userId);

        System.out.println("月活签到key："+userMothLivingKey);

        Long userMothLivingCount = (Long) redisUtil.bitCount(userMothLivingKey);

        return R.ok(userMothLivingCount);

    }


    /**
     * 获取日历组件的签到信息
     *
     * @author flm
     * @return
     */
    @Override
    public R getSignInfo(Long userId) {

        //获取该月的月份
        int moth = LocalDateTimeUtil.queryCurrentMonth();

        //获取该月的总天数(需要考虑闰年的因素）
        int daysOfMonth = LocalDateTimeUtil.getDaysOfMonth(new Date());
        System.out.println("该月天数" + daysOfMonth);

        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(daysOfMonth))
                .valueAt(0);

        //签到key
        String userSignKey = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "month:" + LocalDateTimeUtil.getYYYYMMLocalDate() + ":userId:" + Long.valueOf(userId);


        System.out.println("签到情况key"+userSignKey);

        //通过hashmap存放用户日历组件签到信息
        Map<String, Boolean> signInfo = new TreeMap<>();
        Map<String, Map<String, Boolean>> dateInfo = new HashMap<>();

        List<Long> list = redisTemplate.opsForValue().bitField(userSignKey, bitFieldSubCommands);

        if (list == null || list.isEmpty()) {
            return R.ok(dateInfo);
        }

        long v = list.get(0) == null ? 0 : list.get(0);

        LocalDate today = LocalDate.now();

        for (int i = daysOfMonth; i > 0; i--) {

            //获取日期时间
            LocalDate date = today.withDayOfMonth(i);

            //先右移再左移，若两次位运算的结果与初始状态不同说明该天签到
            boolean flag = v >> 1 << 1 != v;
            signInfo.put(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), flag);

            //右移一位进入前一天
            v >>= 1;

        }

        dateInfo.put("dateInfo", signInfo);

        return R.ok(dateInfo);

    }


    /**
     * 根据用户该月的签到次数实现排行榜
     * @return
     */
    @Override
    public R getUsersSignAmountBySorted() {

        //签到排行榜key
        String RANK_KEY = SignCacheKey.USER_SIGNNOW_CACHE_PREKEY + "rank" + ":month:" + LocalDateTimeUtil.getYYYYMMLocalDate();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisUtil.rangeWithScoreZset(RANK_KEY, 0, 9);

        if (typedTuples == null || typedTuples.size() == 0) {
            return R.ok(HttpStatus.NO_CONTENT, "暂无用户签到");
        }
        List<SignRankInfoVO> rankInfoVOS = new ArrayList<>();
        for (ZSetOperations.TypedTuple<Object> typedTuple : typedTuples) {
            SignRankInfoVO vo = new SignRankInfoVO();
            String string = (String) typedTuple.getValue();
            Integer userId = Integer.valueOf(string);
            Double score = (Double) typedTuple.getScore();

            vo.setUserId(userId);
            vo.setSignAmount(score.intValue());
            User user = userMapper.queryById(userId);
            vo.setUserName(user.getUserName());
            rankInfoVOS.add(vo);
        }

        return R.ok(rankInfoVOS);

    }


    /**
     * 查询缓存中排行榜的信息
     * @param RANK_KEY
     * @return
     */
    private Set<ZSetOperations.TypedTuple<Object>>  querySignRank(String RANK_KEY) {

        Set<ZSetOperations.TypedTuple<Object>> tuples = redisUtil.rangeWithScoreZset(RANK_KEY, 0, 9);



        return tuples;
    }

}
